package com.liuhouse.aspect;

import com.alibaba.fastjson.JSON;
import com.liuhouse.annotation.mySystemlog;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

@Component
@Aspect //告诉spring容器，myLogAspect是切面类
@Slf4j //用于在控制台打印日志信息
public class myLogAspect {

    //确定哪个切点,以后哪个类想成为切点,就给哪个类添加下面那行注解,例如下面的xxpt()就是切点
    @Pointcut("@annotation(com.liuhouse.annotation.mySystemlog)")
    public void xxpt(){

    }

    //定义通知的方法(这里是环绕通知),通知的方法也就是增强的具体代码，@Around注解表示该通知的方法会用在哪个切面
    @Around("xxpt()")
    //ProceedingJoinPoint可以拿到被增强方法的信息
    public Object xxprintLog(ProceedingJoinPoint joinPoint) throws Throwable {
        //proceed方法表示调用目标方法,ret就是目标方法执行完成之后的返回值
        Object ret;
        try {
            //调用下面写的'实现打印日志信息'的方法,执行之前打印的
            xxhandleBefore(joinPoint);
            //这是目标方法执行完成,上一行是目标方法来执行,下一步是目标方法已经执行
            ret = joinPoint.proceed();
            //调用下面写的"实现打印日志信息的数据信息"的方法
            xxhandleAfter(ret);
        } finally {
            //下面的'实现打印日志信息的数据信息'的方法,不管有没有异常都会被执行，确保下面的输出必然输出在控制台
            //System.lineSeparator表示当前系统的换行符
            log.info("==================end================="+System.lineSeparator());
        }
        //封装成切面,然后返回
        return ret;
    }


    //上面的xxprintLog方法执行之前就会调用下面的xxhandleBefore方法
    private void xxhandleBefore(ProceedingJoinPoint joinPoint) {
        //ServletRequestAttributes 是  RequestAttributes 的spring接口的实现类
        //网站的所有的请求信息都存放在 RequestContextHolder 之中了   可以从这个类中获取实例化对象  请求对象  和  响应对象
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        //下面那行就可以拿到请求的报文了,其中我们需要的url,请求方式,ip,这里拿到的request会在下面的log中大量使用
        HttpServletRequest request = requestAttributes.getRequest();
        //获取被增强方法的注解对象,例如获取UserController类的updateUserInfo方法上一行的 @mySystemlog注解
        mySystemlog systemlog = getSystemlog(joinPoint);
        log.info("=============Start======================");
        //下面几个log的输出,第一个参数{}表示占位符,具体的值是第二个参数的变量
        //打印URL
        log.info("请求URL     :   {}" , request.getRequestURL());
        //打印描述信息,例如获取UserController类的updateUserInfo方法上一行的@mySystemlog注解的描述信息
        log.info("接口描述      :   {}" , systemlog.xxbusinessName());
        //打印 Http method
        log.info("请求方式      :   {}" , request.getMethod());
        //打印调用 controller 的全路径(全类名) , 方法名
        log.info("请求类名      :   {}.{}",joinPoint.getSignature().getDeclaringTypeName() ,
                joinPoint.getSignature().getName());
        //打印请求的IP
        log.info("访问IP      :   {}" , request.getRemoteHost());
        //打印请求入参。JSON.toJSONString(joinPoint.getArgs())+FastJson提供的工具方法,能把数组转换成JSON
        log.info("传入参数      : {}" , JSON.toJSONString(joinPoint.getArgs()));

    }

    //获取被增强方法的注解对象,例如获取UserController类的updateUserInfo方法上一行的@mySystemlog注解
    private mySystemlog getSystemlog(ProceedingJoinPoint joinPoint) {
        //Signature是spring提供的接口,MethodSignature是Signature的子接口
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        //mySystemlog使我们写的自定义注解接口
        //下面那行就能获取被增强方法的注解对象,例如获取UserController类的updateUserInfo反复噶上一行的@mySystemlog注解
        mySystemlog systemlog = methodSignature.getMethod().getAnnotation(mySystemlog.class);
        return systemlog;
    }


    //上面的xxprintLog方法执行之后会去调用下面这个xxhandlerAfter方法
    private void xxhandleAfter(Object ret) {
        //打印出参
        log.info("返回参数      : {}" , JSON.toJSONString(ret));
    }

}
